Ma doctrine d'artisan développeur


Journaux liées à cette note :

J'ai lu le très bon billet d'Athoune sur Kloset, moteur de stockage de backup de Plakar #backup, #OnMaPartagé, #JaiDécouvert

Il y a un an, Alexandre m'avait fait découvrir Kopia : Je découvre Kopia, une alternative à Restic.

Ma conclusion était :

Ma doctrine pour le moment : je vais rester sur restic.

source

En septembre 2024, j'ai découvert rustic, un clone de restic recodé en Rust. Pour le moment, je n'ai aucun avis sur rustic.

Il y a quelques semaines, Athoune m'a fait découvrir Plakar, mais je n'avais pas encore pris le temps d'étudier ce que cet outil de backup apportait de plus que restic que j'ai l'habitude d'utiliser.

Depuis, Athoune a eu la bonne idée d'écrire un article très détaillé sur Plakar, enfin, surtout son moteur de stockage avant-gardiste nommé Kloset : "Kloset sur la table de dissection" (au minimum 30 minutes de lecture).

Ce que je retiens, c'est que Kloset propose un système de déduplication plus performant que par exemple celui de restic qui est basé sur Rabin Fingerprints :

For creating a backup, restic scans the source directory for all files, sub-directories and other entries. The data from each file is split into variable length Blobs cut at offsets defined by a sliding window of 64 bytes. The implementation uses Rabin Fingerprints for implementing this Content Defined Chunking (CDC). An irreducible polynomial is selected at random and saved in the file config when a repository is initialized, so that watermark attacks are much harder.

Files smaller than 512 KiB are not split, Blobs are of 512 KiB to 8 MiB in size. The implementation aims for 1 MiB Blob size on average.

For modified files, only modified Blobs have to be saved in a subsequent backup. This even works if bytes are inserted or removed at arbitrary positions within the file.

source

Au moment où j'écris ces lignes, je n'ai aucune idée des différences ou des points communs entre l'algorithme Rolling hash dont parle l'article et Rabin Fingerprints qu'utilise restic.

Chose suprernante, je trouve très peu de citations de Plakar ou kloset sur Hacker News ou Lobster :

Je tiens à remercier Athoune pour l'écriture, qui m'a permis de découvrir de nombreuses choses 🤗.

J'ai découvert le support SSH agent de Bitwarden et ses conséquences sur l'utilisation de Age #password, #secret, #dev-kit, #JaiDécouvert

J'ai utilisé le "Workflow de gestion des secrets d'un projet basé sur Age et des clés ssh" dans un projet professionnel et un collègue a rencontré un problème au niveau du script /scripts/decrypt_secrets.sh :

#!/usr/bin/env bash
set -e

cd "$(dirname "$0")/../"

# Prepare identity arguments for age
identity_args=()
for key in ~/.ssh/id_*; do
    if [ -f "$key" ] && ! [[ "$key" == *.pub ]]; then
        identity_args+=("-i" "$key")
    fi
done

# Execute age with all identity files
age -d "${identity_args[@]}" -o .secret .secret.age

cat << EOF
Secret decrypted in .secret
Don't forget to run the command:

$ source .envrc
EOF

Sa clé privée ssh n'était pas présente dans ~./ssh/ parce qu'il utilise "1Password SSH agent" (disponible depuis mars 2022).
Je ne connaissais pas cette fonctionnalité (merci).

#JaiDécouvert que cette fonctionnalité existe aussi dans Bitwarden depuis février 2025 : "SSH Agent".

#JaiDécouvert qu'une solution alternative pour Bitwarden existait depuis 2020 : bitwarden-ssh-agent. Mais beaucoup moins bien intégré à Bitwarden.


Au cours des 15 dernières années, j'ai régulièrement reçu des demandes de redéploiement de clés SSH de la part des développeurs, parfois plusieurs mois après leur onboarding. La cause principale : la plupart des développeurs ne pensent pas à sauvegarder leurs clés SSH dans leur gestionnaire de password et les perdent inévitablement lors du changement de workstation ou de réinstallation de leur système.

Face à ce constat récurrent, j'envisageais depuis plusieurs années de créer une issue chez Bitwarden pour leur proposer d'implémenter un système de sauvegarde automatique des clés SSH.
L'approche basée sur un ssh-agent ne m'avait jamais traversé l'esprit.


À l'avenir, j'envisage d'intégrer l'usage de Bitwarden SSH Agent (ou équivalent) dans les processus d'onboarding dont j'ai la responsabilité.


J'ai tenté d'ajouter le support de ssh-agent au script /scripts/decrypt_secrets.sh, mais d'après le thread "ssh-agent support", age ne semble pas supporter ssh-agent.

Conséquence : en attendant, j'ai demandé à mon collègue de placer sa clé privée ssh dans ~/.ssh/.

Bilan de poc-sveltekit-custom-server #SvelteKit, #svelte, #node, #javascript, #projet, #POC

Contexte et objectifs

Dans le projet gibbon-replay, j'ai besoin d'exécuter une tâche une fois par jour pour supprimer des anciennes sessions.

gibbon-replay utilise une base de données SQLite qui ne dispose pas nativement de fonctionnalité de type Time To Live, comme on peut trouver dans Clickhouse.
SQLite ne propose pas non plus d'équivalent à pg_cron — ce qui est tout à fait normal étant donnée que SQLite est une librairie et non pas un service à part entière.

Le projet gibbon-replay est un monolith (j'aime les monoliths !) et je souhaite conserver ce choix.

Face à ces contraintes, une solution consiste à intégrer une solution comme Cron for Node.js directement dans l'application gibbon-replay.
Je pense que je dois implémenter cela dans un SvelteKit Custom Server, ce qui me permettrait d'exécuter cette tâche de purge à intervalles réguliers tout en conservant l'architecture monolithique.

Il y a quelques jours, j'ai décidé de tester cette idée dans un POC nommé : poc-sveltekit-custom-server.

J'ai aussi décidé d'expérimenter un objectif supplémentaire dans ce POC : lancer la migration du modèle de données dès le lancement du monolith et non plus lors de la première requête HTTP reçue par le service.

Enfin, je souhaitais ne pas dégrader l'expérience développeur (DX), c'est à dire, je souhaitais pouvoir continuer à simplement utiliser :

$ pnpm run dev

ou

$ pnpm run build
$ pnpm run preview

sans différence avec un projet SvelteKit "vanilla".

Résultats du POC et enseignements

Tout d'abord, le POC fonctionne parfaitement 🙂, sans dégrader l'expérience développeur (DX), qui ressemble à ceci :

$ mise install
$ pnpm install
$ pnpm run load-seed-data
Start data model migration…
Data model migration completed
Start load seed data...
seed data loaded

Lancement du projet en mode développement :

$ pnpm run dev
Start data model migration…
Data model migration completed
Server started on http://localhost:5173 in development mode

Lancement du projet "buildé" :

$ pnpm run build
$ pnpm run preview
Start data model migration…
Data model migration completed
Server started on http://localhost:3000 in production mode

Les migrations et les données "seed.sql" se trouvent dans le dossier /sqls/.

Le SvelteKit Custom Server est implémenté dans le fichier src/server.js et il ressemble à ceci :

import express from 'express';
import cron from 'node-cron';
import db, { migrate } from '@lib/server/db.js';

const isDev = process.env.ENV !== 'production';

migrate(); // Lancement de la migration du modèle de donnée dès de lancement du serveur

// Configuration d'une tâche exécuté toutes les heures
cron.schedule(
    '0 * * * *',
    async () => {
        console.log('Start task...');
        console.log(db().query('SELECT * FROM posts'));
        console.log('Task executed');
    }
);

async function createServer() {
    const app = express();

    ...

Personnellement, je trouve cela simple et minimaliste.

Point de difficulté

SvelteKit utilise des "module alias", comme par exemple $lib.
Problème, par défaut, ces "module alias" ne sont pas configurés lors de l'exécution de node src/server.js.

Pour me permettre d'importer dans src/server.js des modules de src/lib/server/* comme :

import db, { migrate } from '@lib/server/db.js';

j'ai utilisé la librairie esm-module-alias.

Ceci complexifie un peu le projet, j'ai dû configurer ceci dans /package.json :

{
    "scripts": {
        "dev": "ENV=development node --loader esm-module-alias/loader --no-warnings src/server.js",
        "preview": "ENV=production node --loader esm-module-alias/loader --no-warnings build/server.js",
        
    ...

	"aliases": {
        "@lib": "src/lib/"
    }
}
  • ajout de --loader esm-module-alias/loader --no-warnings
  • et la section aliases

Et dans /vite.config.js :

export default defineConfig({
    plugins: [sveltekit()],
    resolve: {
        alias: {
          '@lib': path.resolve('./src/lib')
        }
    }
});
  • ajout de alias

Le fichier src/server.js contient du code spécifique en fonction de son contexte d'exécution ("dev" ou "buildé") :

    if (isDev) {
        const { createServer: createViteServer } = await import('vite');

        const vite = await createViteServer({
            server: { middlewareMode: true },
            appType: 'custom'
        });

        app.use(vite.middlewares);
    } else {
        const { handler } = await import('./handler.js');
        app.use(handler);
    }

En mode "dev" il utilise Vite et en "buildé" il utilise le fichier build/handler.js généré par SvelteKit build en mode SSR.

Le fichier src/server.js est copié vers le dossier /build/ lors de l'exécution de pnpm run build.

J'ai testé le bon fonctionnement du POC dans un container Docker.

J'ai intégré au projet un deployment-playground : https://github.com/stephane-klein/poc-sveltekit-custom-server/tree/main/deployment-playground.

La suite...

Je souhaite rédiger cette note en anglais et la publier sur https://github.com/sveltejs/kit/discussions et https://old.reddit.com/r/sveltejs/ afin :

  • d'avoir des retours d'expérience
  • de découvrir des méthodes alternatives
  • et partager la méthode que j'ai utilisée, qui sera peut-être utile à d'autres développeurs Svelte 🙂

Update du 2025-05-29 à 00:07 - Je viens de publier ceci :


2025-05-29 : voir J'ai découvert la fonctionnalité SvelteKit Shared hooks init

Faut-il encore configurer du swap en 2025, même sur des serveurs avec beaucoup de RAM ? #OnMePoseLaQuestion, #DevOps, #admin-sys, #linux, #JaiDécouvert, #JaiLu

Aujourd'hui, j'ai implémenté des tests de montée en charge à l'aide de Grafana k6. En ciblant un site web hébergé sur un petit serveur Scaleway DEV1-M, j'ai constaté que le serveur est devenu inaccessible à la fin des tests. Aucun swap n'était configuré sur cette Virtual machine de 4Go de RAM.

Je me suis souvenu qu'en 2019, j'ai rencontré aussi des problèmes de freeze sur une VM AWS EC2 que j'ai corrigés en ajoutant un peu de swap au serveur. Après cela, je n'ai constaté plus aucun freeze de VM pendant 4 ans.

Ce sujet de swap m'a fait penser à la question qu'un ami m'a posée en octobre 2024 :

Désactiver le swap sur une Debian, recommandé ou pas ?

Alors que j'ai 29Go utilisé sur 64, le swap était plein (3,5Go occupé à 100%), les 12 cœurs du serveur partaient dans les tours. J'ai désactivé le swap et me voilà gentiment avec un load average raisonnable, pour les tâches de cette machine.

C'est une très bonne question que je me pose depuis longtemps. J'ai enfin pris un peu de temps pour creuser ce sujet.

Sept mois plus tard, voici ma réponse dans cette note 😉.

#JaiDécouvert le paramètre kernel nommé Swappiness.

swappiness

This control is used to define how aggressive the kernel will swap memory pages. Higher values will increase aggressiveness, lower values decrease the amount of swap. A value of 0 instructs the kernel not to initiate swap until the amount of free and file-backed pages is less than the high water mark in a zone.

The default value is 60.

documentation du kernel

Dans la documentation SwapFaq d'Ubuntu j'ai lu :

The swappiness parameter controls the tendency of the kernel to move processes out of physical memory and onto the swap disk. Because disks are much slower than RAM, this can lead to slower response times for system and applications if processes are too aggressively moved out of memory.

  • swappiness can have a value of between 0 and 100
  • swappiness=0 tells the kernel to avoid swapping processes out of physical memory for as long as possible
  • swappiness=100 tells the kernel to aggressively swap processes out of physical memory and move them to swap cache

The default setting in Ubuntu is swappiness=60. Reducing the default value of swappiness will probably improve overall performance for a typical Ubuntu desktop installation. A value of swappiness=10 is recommended, but feel free to experiment. Note: Ubuntu server installations have different performance requirements to desktop systems, and the default value of 60 is likely more suitable.

source

D'après ce que j'ai compris, plus swappiness tend vers zéro, moins le swap est utilisé.

J'ai lu ici :

vm.swappiness = 60 : Valeur par défaut de Linux : à partir de 40% d’occupation de Ram, le noyau écrit sur le disque.

source

Cependant, je n'ai pas trouvé d'autres sources qui confirment cette correspondance entre la valeur de swappiness et un pourcentage précis d'utilisation de la RAM.

J'ai ensuite cherché à savoir si c'était encore pertinent de configurer du swap en 2025, sur des serveurs qui disposent de beaucoup de RAM.

#JaiLu ce thread : "Do I need swap space if I have more than enough amount of RAM?", et voici un extrait qui peut servir de conclusion :

In other words, by disabling swap you gain nothing, but you limit the operation system's number of useful options in dealing with a memory request. Which might not be, but very possibly may be a disadvantage (and will never be an advantage).

source

Je pense que ceci est d'autant plus vrai si le paramètre swappiness est bien configuré.

Concernant la taille du swap recommandée par rapport à la RAM du serveur, la documentation de Ubuntu conseille les ratios suivants :

       RAM      Swap    Maximum Swap
      256MB    256MB           512MB 
      512MB    512MB          1024MB
     1024MB   1024MB          2048MB
        1GB      1GB             2GB
        2GB      1GB             4GB
        3GB      2GB             6GB
        4GB      2GB             8GB
        5GB      2GB            10GB
        6GB      2GB            12GB
        8GB      3GB            16GB
       12GB      3GB            24GB
       16GB      4GB            32GB
       24GB      5GB            48GB
       32GB      6GB            64GB
       64GB      8GB           128GB
      128GB     11GB           256GB
      256GB     16GB           512GB
      512GB     23GB             1TB
        1TB     32GB             2TB
        2TB     46GB             4TB
        4TB     64GB             8TB
        8TB     91GB            16TB

source

#JaiDécouvert aussi que depuis le kernel 2.6, les fichiers de swap sont aussi rapides que les partitions de swap :

Definitely not. With the 2.6 kernel, "a swap file is just as fast as a swap partition."

source

Suite à ces apprentissages, j'ai configuré et activé un swap de 2G sur la VM Scaleway DEV1-L équipée de 4G de RAM, avec le paramètre swappiness réglé à 10.

J'ai relancé mon test Grafana k6 et je n'ai constaté plus aucun freeze, je n'ai pas perdu l'accès au serveur.

De plus, probablement grâce au paramètre swappiness fixé à 10, j'ai observé que le swap n'a pas été utilisé pendant le test.

Suite à ces lectures et à cette expérience concluante, j'ai décidé de désormais configurer systématiquement du swap sur tous mes serveurs de la manière suivante :

if swapon --show | grep -q "^/swapfile"; then
    echo "Swap is already configured"
else
    get_swap_size() {
        local ram_gb=$(free -g | awk '/^Mem:/ {print $2}')
        
        # Why this values? See https://help.ubuntu.com/community/SwapFaq#How_much_swap_do_I_need.3F
        if [ $ram_gb -le 1 ]; then
            echo "1G"
        elif [ $ram_gb -le 2 ]; then
            echo "1G"
        elif [ $ram_gb -le 6 ]; then
            echo "2G"
        elif [ $ram_gb -le 12 ]; then
            echo "3G"
        elif [ $ram_gb -le 16 ]; then
            echo "4G"
        elif [ $ram_gb -le 24 ]; then
            echo "5G"
        elif [ $ram_gb -le 32 ]; then
            echo "6G"
        elif [ $ram_gb -le 64 ]; then
            echo "8G"
        elif [ $ram_gb -le 128 ]; then
            echo "11G"
        else
            echo "11G"
        fi
    }

    SWAP_SIZE=$(get_swap_size)
    fallocate -l $SWAP_SIZE /swapfile
    chmod 600 /swapfile
    mkswap /swapfile
    swapon /swapfile

    if ! grep -q "^/swapfile.*swap" /etc/fstab; then
        echo "/swapfile none swap sw 0 0" >> /etc/fstab
    fi
fi

# Why 10 instead default 60? see https://help.ubuntu.com/community/SwapFaq#:~:text=a%20value%20of%20swappiness%3D10%20is%20recommended
echo 10 | tee /proc/sys/vm/swappiness
echo "vm.swappiness=10" | tee -a /etc/sysctl.conf

Nom et arborescence de Monorepo #monorepo, #software-engineering

Je suis un adepe du paradigme Monorepo, de la documentation colocalisée et des issues colocalisées.

Je conseille de nommer le repository avec le nom de l'organisation.

Par exemple, si mon organisation se nomme « Dummy Tech » et si elle utilise GitLab, alors je conseille d'utiliser le slug dummy-tech, ce qui donne gitlab.com/dummy-tech/dummy-tech/ et « Dummy Tech Monorepo » comme titre de repository.

La neutralité de ce nom facilite les décisions concernant ce qui peut ou non être inclus dans le repository, sans être limité par un nom trop restrictif. Il est flexible face à l'évolution du projet. Il permet d'éviter bien des débats à propos du nommage.

En 2018, sur GitHub, j'ai découvert un exemple de monorepo d'une organisation. Cet exemple m'a servi de base et je l'ai fait évoluer quand je travaillais chez Spacefill.

Voici un exemple d'arborescence de monorepo que j'aime utiliser :

dummy-tech
├── deployments
│   ├── prod
│   └── sandbox
├── ci
├── docs
├── playgrounds
│   ├── playground_a
│   └── playground_b
├── services
│   ├── service_a
│   ├── service_b
│   └── service_c
└── tools
    ├── tool_a
    ├── tool_b
    └── tool_c

J'ai publié le projet "pg_back-docker-sidecar" #iteration, #projet, #postgresql, #backup, #JaimeraisUnJour

Je viens de terminer une première itération de travail sur Projet 27 - "Créer un POC de pg_back".

Le résultat se trouve dans le repository GitHub : pg_back-docker-sidecar

J'ai passé en tout 17 h 30 sur ce projet, écriture de notes incluse.

Ce projet a évolué par rapport à mon objectif initial :

Initialement, dans ce dépôt, je voulais tester l'implémentation de pg_back déployé dans un conteneur Docker comme un « sidecar » pour sauvegarder une base de données PostgreSQL déployée via Docker.

Et progressivement, j'ai changé l'objectif de ce projet. Il contient maintenant

source

Voici tous les éléments testés dans le tutoriel :

  • pg_back est dépolyé dans un Docker sidecar
  • L'instance PostgreSQL est sauvegardée dans une instance Minio
  • Les archives sont chiffrées avec age
  • Les archives sont générées au format custom
  • J'ai documenté une méthode pour télécharger une archive dans un dossier du workspace du développeur
  • J'ai documenté une méthode pour restaurer l'archive dans un serveur PostgreSQL déployé via Docker
  • J'ai testé le fonctionnement du système d'expiration des archives
  • J'ai testé la fonctionnalité de "purge" automatique

Éléments que j'ai implémentés

L'image Docker proposée par pg_back ne contient pas de scheduler de type cron et ne suit pas les recommandations The Twelve-Factors App.

J'ai décidé d'implémenter ma propre image Docker stephaneklein/pg_back-docker-sidecar:2.5.0-delete-local-file-after-upload avec les ajouts suivants :

  • Support de configuration basé sur des variables d'environnement, par exemple :
  pg_back:
    image: stephaneklein/pg_back-docker-sidecar:2.5.0-delete-local-file-after-upload
    environment:
      POSTGRES_HOST: postgres1
      POSTGRES_PORT: 5432
      POSTGRES_USER: postgres
      POSTGRES_DBNAME: postgres
      POSTGRES_PASSWORD: password
      
      BACKUP_CRON: ${BACKUP_CRON:-0 3 * * *}
      UPLOAD: "s3"
      UPLOAD_PREFIX: "foobar"
      ...
  • Intégration de Supercronic pour exécuter pg_back régulièrement, une fonctionnalité de type cron

Patch envoyé en upstream

J'ai proposé deux patchs à pg_back :

Le premier patch est totalement mineur.

Dans la version actuelle 2.5.0 de pg_back, les archives dump ne sont pas supprimées du filesystem de container après l'upload vers l'Object Storage.
Ce choix me perturbe, car je préfère éviter de surcharger le disque avec des fichiers d'archives volumineux qui risquent de saturer l'espace disponible.

Pour éviter cela, j'ai implémenté "Add the --delete-local-file-after-upload to delete local file after upload" qui permet de supprimer les fichiers intermédiaires après upload.

Bilan

J'ai réussi à effectuer un cycle complet de la sauvegarde à la restauration.
J'ai décidé d'utiliser pg_back pour mes sauvegardes PostgreSQL automatique vers Object Storage.

J'ai déprécié le projet restic-pg_dump-docker pour inviter à utiliser pg_back.

Idée d'amélioration

#JaimeraisUnJour créer et implémenter les issues suivantes.

1. Implémenter une commande pg_back snapshots pour lister les snapshots sous une forme facilement lisible par un humain. Actuellement, le retour de la commande ressemble à ceci :

$ pg_back --list-remote s3
foobar/hba_file_2025-04-14T14:58:08Z.out.age
foobar/hba_file_2025-04-14T14:58:39Z.out.age
foobar/ident_file_2025-04-14T14:58:08Z.out.age
foobar/ident_file_2025-04-14T14:58:39Z.out.age
foobar/pg_globals_2025-04-14T14:58:08Z.sql.age
foobar/pg_globals_2025-04-14T14:58:39Z.sql.age
foobar/pg_settings_2025-04-14T14:58:08Z.out.age
foobar/pg_settings_2025-04-14T14:58:39Z.out.age
foobar/postgres_2025-04-14T14:58:08Z.dump.age
foobar/postgres_2025-04-14T14:58:39Z.dump.age

Je ne trouve pas ce rendu agréable à lire. J'aimerais afficher quelque chose qui ressemble à la sortie de restic. Par exemple :

$ pg_back snapshots
ID        Date                 Folder
---------------------------------------
40dc1520  2025-04-14 14:58:08  foobar
79766175  2025-04-14 14:58:39  foobar

2. Implémenter un système de suppressions des archives basé sur des règles plus avancées, comme celle de restic

3. Implémenter un refactoring vers cobra pour utiliser des sous-commandes (subcommands) et éviter le mélange entre paramètres et commandes.

Journal du samedi 22 mars 2025 à 10:58 #project-management, #gestion-projet, #software-engineering, #Doctrine

Voici quelques principes qui me guident. Je pense qu'ils contribuent à rendre une organisation efficace et efficiente.

1. La loi empirique de Gall :

« Un système complexe qui fonctionne se trouve invariablement avoir évolué depuis un système simple qui fonctionnait.
La proposition inverse se révèle également exacte : Un système complexe développé de A à Z ne fonctionne jamais et vous n'arriverez jamais à le faire fonctionner. Vous devez recommencer depuis le début, en commençant par un système simple. »

source

2. Je suis convaincu de la pertinence du modèle de Tuckman pour comprendre comment les équipes se construisent et évoluent au fil du temps. En conséquence, je crois qu'une organisation doit accorder suffisamment de temps aux équipes pour qu'elles atteignent leur phase de performance, puis veiller à maintenir la composition de l'équipe sur la durée.

3. Le modèle du triangle de gestion de projet qui je trouve est très bien expliqué dans le livre Getting Real :

Voici un moyen simple de lancer le projet dans les délais et le budget impartis : ne pas les modifier. Il ne faut jamais consacrer plus de temps ou d'argent à un problème, mais simplement en réduire la taille (le périmètre).

Il existe un mythe qui dit que l'on peut lancer un projet dans les délais, en respectant le budget et le champ d'application. Cela n'arrive presque jamais et, lorsque c'est le cas, la qualité s'en ressent souvent.

Si vous ne pouvez pas tout faire tenir dans le temps et le budget impartis, n'augmentez pas le temps et le budget. Au contraire, réduisez le champ d'application. Il sera toujours temps d'ajouter des choses plus tard - plus tard est éternel, maintenant est éphémère.

Il vaut mieux lancer quelque chose d'excellent dont la portée est un peu plus réduite que prévu que de lancer quelque chose de médiocre et plein de trous parce qu'il fallait respecter une fenêtre magique de temps, de budget et de portée. Laissez la magie à Houdini. Vous avez une véritable entreprise à gérer et un véritable produit à livrer.

source

Journal du jeudi 27 février 2025 à 10:41 #golang, #DevOps, #selfhosting, #JaiDécouvert, #JaimeraisUnJour

En travaillant sur la note "Je découvre Beszel", #JaiDécouvert ici un autre projet de monitoring intéressant : Dozzle (https://dozzle.dev/).

Dozzle is a small lightweight application with a web based interface to monitor Docker logs. It doesn’t store any log files. It is for live monitoring of your container logs only.

source

Là aussi, Dozzle est un projet en Golang, commencé fin 2018.

Dozzle est une alternative à Loki + Grafana.

#JaimeraisUnJour déployer Dozzle pour le tester et si ce test est concluant, je l'intégrerai peut-être à ma stack minimaliste de monitoring en complément de Beszel et Gatus.

Je découvre Beszel #JaiDécouvert, #OnMaPartagé, #monitoring, #DevOps, #Doctrine

Un ami m'a partagé le projet Beszel (https://beszel.dev/).

Beszel is a lightweight server monitoring platform that includes Docker statistics, historical data, and alert functions.

It has a friendly web interface, simple configuration, and is ready to use out of the box. It supports automatic backup, multi-user, OAuth authentication, and API access.

source

Beszel est codé en Golang, il est très récent, il a commencé en été 2024, c'est sans doute pour cela que je ne l'avais jamais croisé.

De prime abord, j'ai pensé que Beszel était un outil de Status / Uptime pages comme Uptime Kuma ou Gatus, mais ce n'est pas le cas.

Je qualifierai plutôt Beszel d'alternative "plug and play" de Prometheus + Grafana + node_exporter + cAdvisor.

Alors que l'annonce de Beszel a fait "choux blanc" sur Hacker News « Beszel: Lightweight server resource monitoring with history, Docker stats,alerts », le projet a suscité plus de réaction — 270 commentaires — sur Subreddit SelfHosted : « I just released Beszel, a server monitoring hub with historical data, docker stats, and alerts. It's a lighter and simpler alternative to Grafana + Prometheus or Checkmk. Any feedback is appreciated! ».

Les retours sont très positifs 🙂 :

« There is beauty in simplicity. Very nice little application! »

« Kiss »

« Just installed on all of my servers, gorgeous project, simple but also not simple. »

« Awesome work. I think you identified a good use case for the self hosting community, a simple server monitor running as a simple service. I will give it a go soon! »

« I never installed Grafana and Prometheus because it’s overkill for my little server.. but this looks really good! I’ll give it a go »

Prometheus propose bien plus d'exporter que Beszel, mais je pense que Beszel est un bon point de départ pour une stack de monitoring minimaliste.

À l'avenir, mon choix par défaut en matière monitoring sera probablement un couple Beszel + Gatus. Si des besoins plus avancés émergent, comme du monitoring poussé de PostgreSQL, Redis ou d'autres services, j'envisagerai alors de commencer la mise en place du couple Prometheus + Grafana.

Journal du mardi 25 février 2025 à 09:35 #Doctrine, #documentation, #shell, #terminal, #dev-kit, #markdown

Dans cette note, je souhaite décrire et expliquer comment j'intègre des sessions de terminal dans des documentations.

Convention mainstream que j'ai adoptée

Je suis la convention mainstream suivante pour représenter des sessions terminal (de type bash, zsh…) :

Première partie :

$ echo "Hello, world!"
Hello, world!

$ ls -l
total 0
-rw-r--r-- 1 user user 0 Feb 25 10:00 file.txt

Seconde partie :

$ sudo su
# systemctl restart nginx 
$ exit

$ uptime # Affiche l'uptime
uptime 10:00:00 up 1 day, 2:45, 1 user, load average: 0.15, 0.10, 0.05

Les lignes commençant par $ ou # indiquent les commandes saisies par l'utilisateur, tandis que les autres correspondent aux sorties du terminal.
Il m'arrive également d'ajouter des commentaires en fin de ligne à l'aide de # ….

Origine de cette convention ?

J'ai cherché à retracer l'origine de cette pratique et elle semble très ancienne, probablement adoptée dès les débuts d'Unix en 1971.
On retrouve cette syntaxe notamment dans le livre The Unix Programming Environment (pdf), publié en 1984.


Ne pas inclure de $ si un bloc ne contient aucune sortie de commande

La règle MD014 de Markdownlint aborde ce sujet :

MD014 - Dollar signs used before commands without showing output

Tags: code

Aliases: commands-show-output

This rule is triggered when there are code blocks showing shell commands to be typed, and the shell commands are preceded by dollar signs ($):

$ ls
$ cat foo
$ less bar

The dollar signs are unnecessary in the above situation, and should not be included:

ls
cat foo
less bar

However, an exception is made when there is a need to distinguish between typed commands and command output, as in the following example:

$ ls
foo bar
$ cat foo
Hello world
$ cat bar
baz

Rationale: it is easier to copy and paste and less noisy if the dollar signs are omitted when they are not needed. See https://cirosantilli.com/markdown-style-guide#dollar-signs-in-shell-code for more information.

source

Pendant un certain temps, j’ai suivi la recommandation de ne pas inclure de $ dans un bloc de commande lorsqu’il n’affiche aucune sortie.

Après quelques années, j’ai décidé de ne plus appliquer cette règle pour la raison suivante :

  • Lorsque la documentation mélange des fichiers de configuration et des commandes sans $, j’ai du mal à distinguer ce qui doit être exécuté dans un terminal et ce qui relève de la configuration.

Pour éviter toute ambiguïté, j’ai adopté une approche radicale : toujours utiliser $ pour indiquer une commande à exécuter, même si le bloc ne contient pas de sortie.

Je n'aime pas les $ dans les blocs de commande, car que je ne peux pas copier / coller facilement !

Je comprends tout à fait cette remarque. Cependant, ces "screenshots" de session de terminal n'ont pas vocation à être copiées / collées en masse.
Ils ont avant tout une fonction explicative.
Ce sont des sortes de "screenshot".

Si j'éprouve le besoin de faire souvent des "copier / coller" d'exemples de session terminal, alors je considère que c'est le signe qu'un script "helper" serait plus approprié pour exécuter ce groupe de commandes d’un seul coup.

Par exemple, je peux remplacer ce contenu :

Ensure that the following prerequisites have been installed: mise and direnv, see instruction in ../README.md.

$ mise trust
$ mise install
$ scw version
Version    2.34.0

Par celui-ci (qui indique l'existence du script ./scripts/mise-install.sh) :

Ensure that the following prerequisites have been installed: mise and direnv, see instruction in ../README.md (or execute ./scripts/mise-install.sh).

$ mise trust
$ mise install
$ scw version
Version    2.34.0

Cela évite les copier-coller répétitifs tout en conservant la clarté de l’explication.